#pragma once

#include <type_traits>

#include <utils/Concepts.h>
#include <utils/bit_utils/CudaWrapper.h>

#pragma push_macro("SWAP")

#define SWAP(a, b) (a ^ b) && ((b ^= a ^= b, a ^= b))

namespace solar::bit
{
    namespace internal
    {
#if !(defined(_MSC_VER) && !defined(__clang__))
        constexpr auto kSizeOfFloat128 = 16;
#endif
        constexpr void swapBool(bool& num1, bool& num2) { SWAP(num1, num2); }
        constexpr void swapChar(char& num1, char& num2) { SWAP(num1, num2); }
        constexpr void swapShort(short& num1, short& num2) { SWAP(num1, num2); }
        constexpr void swapInt(int& num1, int& num2) { SWAP(num1, num2); }
        constexpr void swapLongLong(long long& num1, long long& num2) { SWAP(num1, num2); }
#if !(defined(_MSC_VER) && !defined(__clang__))
        constexpr void swapInt128(__int128& num1, __int128& num2) { SWAP(num1, num2); }
#endif
        inline __host__ __device__ void swapFloat(float& num1, float& num2)
        {
            auto& num1_i = reinterpret_cast<int&>(num1);
            auto& num2_i = reinterpret_cast<int&>(num2);
            SWAP(num1_i, num2_i);
        }
        inline __host__ __device__ void swapDouble(double& num1, double& num2)
        {
            auto& num1_i = reinterpret_cast<long long&>(num1);
            auto& num2_i = reinterpret_cast<long long&>(num2);
            SWAP(num1_i, num2_i);
        }
#if !(defined(_MSC_VER) && !defined(__clang__))
        inline __host__ __device__ void swapFloat128(long double& num1, long double& num2)
        {
            auto& num1_i = reinterpret_cast<__int128&>(num1);
            auto& num2_i = reinterpret_cast<__int128&>(num2);
            SWAP(num1_i, num2_i);
        }
#endif
    } // namespace internal

    template <typename T1, typename T2>
        requires std::is_same_v<std::remove_cvref_t<class T1>, std::remove_cvref_t<class T2>>
    constexpr void swap(T1& /*num1*/, T2& /*num2*/)
    {
        static_assert(false, "Unsupported input type");
    }

    template <Number T1, Number T2>
        requires std::is_same_v<std::remove_cvref_t<class T1>, std::remove_cvref_t<class T2>>
    inline __host__ __device__ void swap(T1& num1, T2& num2)
    {
        using raw_type = std::remove_cvref_t<class T1>;
        if constexpr (std::is_same_v<bool, raw_type>)
        {
            internal::swapBool(num1, num2);
        }
        if constexpr (std::is_same_v<char, raw_type> || std::is_same_v<unsigned char, raw_type>)
        {
            internal::swapChar(num1, num2);
        }
        if constexpr (std::is_same_v<short, raw_type> || std::is_same_v<unsigned short, raw_type>)
        {
            internal::swapShort(num1, num2);
        }
        if constexpr (std::is_same_v<int, raw_type> || std::is_same_v<unsigned int, raw_type>)
        {
            internal::swapInt(num1, num2);
        }
        if constexpr (std::is_same_v<long long, raw_type> ||
                      std::is_same_v<unsigned long long, raw_type>)
        {
            internal::swapLongLong(num1, num2);
        }
#if !(defined(_MSC_VER) && !defined(__clang__))
        if constexpr (std::is_same_v<__int128, raw_type> ||
                      std::is_same_v<unsigned __int128, raw_type>)
        {
            internal::swapInt128(num1, num2);
        }
#endif
        if constexpr (std::is_same_v<float, raw_type>)
        {
            internal::swapFloat(num1, num2);
        }
        if constexpr (std::is_same_v<double, raw_type> || (std::is_same_v<long double, raw_type> &&
                                                           sizeof(long double) == sizeof(double)))
        {
            internal::swapDouble(num1, num2);
        }
#if !(defined(_MSC_VER) && !defined(__clang__))
        if constexpr (std::is_same_v<long double, raw_type> &&
                      sizeof(long double) == internal::kSizeOfFloat128)
        {
            internal::swapFloat128(num1, num2);
        }
#endif
    }

} // namespace solar::bit

#pragma pop_macro("SWAP")
